﻿using System.ComponentModel;

namespace Swimj.Core.Utilities
{
    public static class Enum<T> where T : struct
    {
        public static string DescriptionFor(T value)
        {
            var type = value.GetType();
            if (!type.IsEnum)
            {
                throw new ArgumentException("Value must be of Enum type.");
            }

            var fi = type.GetField(value.ToString());
            var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);

            return attributes.Length > 0 ? attributes[0].Description : value.ToString();
        }

        public static IEnumerable<T> Values => Enum.GetValues(typeof(T)).Cast<T>();

        public static IEnumerable<T> Except(IEnumerable<T> sequence)
        {
            return Values.Except(sequence);
        }

        public static IEnumerable<T> Except(params T[] sequence)
        {
            return Values.Except(sequence);
        }

        public static T[] ToArray()
        {
            return Values.ToArray();
        }

        public static T Parse(string name, bool ignoreCase = false)
        {
            return (T)Enum.Parse(typeof(T), name, ignoreCase);
        }

        public static T? TryParse(string name, bool ignoreCase = false)
        {
            return !string.IsNullOrEmpty(name) && TryParse(name, out var value, ignoreCase) ? (T?)value : null;
        }

        public static bool TryParse(string name, out T value, bool ignoreCase = false)
        {
            try
            {
                value = Parse(name, ignoreCase);
                return true;
            }
            catch (ArgumentException)
            {
                value = default(T);
                return false;
            }
        }

        public static IEnumerable<T> GetValues()
        {
            return Enum.GetValues(typeof(T)).Cast<T>();
        }

        public static string GetName(T value)
        {
            return Enum.GetName(typeof(T), value);
        }

        public static IEnumerable<TOutput> ConvertAll<TOutput>()
        {
            try
            {
                return GetValues().Select(e => (TOutput)Convert.ChangeType(e, typeof(TOutput)));
            }
            catch (Exception)
            {
                return Enumerable.Empty<TOutput>();
            }
        }

        public static bool TryGetEnumValue(int value, out T? enumValue)
        {
            enumValue = Enum.GetValues(typeof(T)).Cast<T?>()
                .SingleOrDefault(e => (int)Convert.ChangeType(e, typeof(int)) == value);
            return enumValue.HasValue;
        }

        public static IDictionary<T, string> GetDictionary()
        {
            return GetValues().ToDictionary(e => e, GetName);
        }

        public static Dictionary<T, IEnumerable<TAttribute>> GetDictionaryAttributes<TAttribute>(bool inherit = false)
            where TAttribute : Attribute
        {
            return Enum.GetValues(typeof(T)).OfType<T>()
                .ToDictionary(e => e, e => GetAttributes<TAttribute>(e, inherit));
        }

        public static IEnumerable<TAttribute> GetAttributes<TAttribute>(T value, bool inherit) where TAttribute : Attribute
        {
            return inherit
                ? value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(TAttribute), false)
                    .OfType<TAttribute>()
                : value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(TAttribute), false)
                    .Where(a => a.GetType().IsAssignableFrom(typeof(TAttribute))).OfType<TAttribute>();
        }

        public static IEnumerable<TAttribute> GetAttributes<TAttribute>(T value) where TAttribute : Attribute
        {
            return GetAttributes<TAttribute>(value, false);
        }
    }
}

